Network Extension

RSS for tag

Customize and extend the core networking features of iOS, iPad OS, and macOS using Network Extension.

Posts under Network Extension tag

200 Posts

Post

Replies

Boosts

Views

Activity

Network Extension Resources
General: Forums subtopic: App & System Services > Networking DevForums tag: Network Extension Network Extension framework documentation Routing your VPN network traffic article Filtering Network Traffic sample code TN3120 Expected use cases for Network Extension packet tunnel providers technote TN3134 Network Extension provider deployment technote TN3165 Packet Filter is not API technote Network Extension and VPN Glossary forums post Debugging a Network Extension Provider forums post Exporting a Developer ID Network Extension forums post Network Extension Framework Entitlements forums post Network Extension vs ad hoc techniques on macOS forums post Network Extension Provider Packaging forums post NWEndpoint History and Advice forums post Extra-ordinary Networking forums post URL filter: WWDC 2025 Session 234 Filter and tunnel network traffic with NetworkExtension URL filters documentation Filtering traffic by URL sample code Setting up a PIR server for URL filtering sample code Using the Bloom filter tool to configure a URL filter sample code PIR Service Example open source server sample and specifically its documentation Wi-Fi management: Understanding NEHotspotConfigurationErrorInternal forums post See also Networking Resources for general networking resources, including information about Wi-Fi. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com"
0
0
4.1k
Aug ’26
sysextd: "no policy, cannot allow apps outside /Applications" - NEFilterDataProvider system extension on macOS 26
I'm developing a macOS security tool using NEFilterDataProvider as a system extension. On macOS 26 beta (25E241), sysextd consistently rejects my extension with: sysextd: no policy, cannot allow apps outside /Applications Configuration: App installed in /Applications/ Signed with Developer ID Application (693DSH8GN5) Entitlement: com.apple.developer.networking.networkextension = content-filter-provider com.apple.developer.system-extension.install = true Developer Mode enabled on test machine Comparison with Little Snitch: Little Snitch runs correctly on the same machine. Key differences I found: Little Snitch uses content-filter-provider-systemextension instead of content-filter-provider Little Snitch has com.apple.security.app-sandbox = false Both signed with Developer ID Application When I switch to content-filter-provider-systemextension, Xcode rejects every provisioning profile because none match that entitlement value, and the Developer Portal doesn't expose fine-grained control over the Network Extensions array values. Questions Is content-filter-provider-systemextension the correct entitlement for system extensions on macOS 26? How should the provisioning profile be configured to support it? Is there a known sysextd issue on macOS 26 beta causing this regardless of configuration? Is there - somewhere! - a guide on how to build such an extension? Thanks in advance for your help.
4
0
702
19h
NEURLFilter / SimpleURLFilter: neagent fails to open URL prefilter mmap file with errno 13 Permission denied
I am testing NEURLFilter on macOS using the SimpleURLFilter sample, and I am seeing a failure from neagent while it is saving the local URL prefilter Bloom filter to its mmap file. The relevant log is: neagent +[NEBloomFilter mmapToFile:data:dataLength:numberOfBits:numberOfHashes:murmurSeed:tag:]: NEBloomFilter - failed to open mmap file /private/var/db/urlPrefilter/com.apple.networkextension.url-prefilter-data.temp.com.example.apple-samplecode.SimpleURLFilterTC3Q7MAJXF <errno 13 - Permission denied> neagent <NEAgentURLFilterExtension: 0xc8ce64280>: -[NEAgentURLFilterExtension startURLFilter]_block_invoke - Failed to save first fetch of pre-filter data Environment: macOS: 26.5.1 (25F80) Xcode: 26.5 (17F42) Platform: macOS Signing type: Apple Development (automatically manage signing) What I am doing: Build and run the containing app. Save and enable the NEURLFilterManager configuration. The URL filter provider starts. The provider's prefilter code is reached. neagent logs the mmap failure above while trying to open a temporary file under /private/var/db/urlPrefilter. Expected result: neagent should be able to create or open its system-managed URL prefilter cache / mmap file under /private/var/db/urlPrefilter, and the local Bloom filter should be loaded successfully. Actual result: neagent fails to open the temporary mmap file with errno 13 Permission denied: /private/var/db/urlPrefilter/com.apple.networkextension.url-prefilter-data.temp.<bundle/team-specific suffix> I am not manually creating, modifying, or chmod/chown-ing /private/var/db/urlPrefilter or anything inside it. The directory and its contents are entirely system-managed. The failure appears to happen inside neagent while it is handling the system-managed URL prefilter cache. The failure occurs at the mmapToFile: step while neagent saves the Bloom filter prefilter data. Directory state: drwxr-xr-x 2 root wheel 64 /private/var/db/urlPrefilter Has anyone else encountered this? Any suggestions on what could cause neagent to fail with errno 13 on its own mmap file under /private/var/db/urlPrefilter?
9
2
1.1k
20h
Appropriate API for measuring device-wide network traffic on iOS
I am developing a consumer iOS app for App Store distribution and would like to confirm the appropriate public API for the following use case. The app needs to measure the amount of network traffic passing through the device over short time intervals, for example once per second or more frequently. The app does not need to inspect packet contents, block or filter traffic, or provide a remote VPN service. It also should not generate dedicated network traffic solely for speed measurement. The goal is only to observe device-wide network traffic volume and convert that information into a simple real-time indicator for the user. I am currently investigating the Network Extension framework. Would NEPacketTunnelProvider be an appropriate API for this use case? If not, is there another supported Network Extension provider or other public iOS API intended for measuring device-wide network traffic in this manner? I would like to choose an architecture that is technically supported by Apple and appropriate for a consumer app distributed through the App Store before beginning implementation.
0
0
27
20h
Read file with System Network Extension from App Group
I have trouble with reading a file from an App Group with my System Network Extension. The app group container is found successfully. However the file read returns empty. In the app itself the same code runs fine and returns a string array of items found in the file. Code: func readFile() - [String] {         var jsonResult: [String] = []         guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: AppConstants.groupID) else {             fatalError()         }         let fileURL = containerURL.appendingPathComponent("file.json")         if let data = try? NSData(contentsOfFile: fileURL.path, options: .mappedIfSafe) as Data {             if let json = try? JSONSerialization.jsonObject(with: data, options: .fragmentsAllowed) {                 jsonResult = json as! [String]             }         }         os_log("jsonResult: %{public}@", jsonResult)         return jsonResult     } Log: default 09:42:19.486793+0200 app-network-extension container_create_or_lookup_app_group_path_by_app_group_ identifier: success default 09:42:20.105792+0200 app-network-extension jsonResult: ( ) Edit, after more digging: fileURL is different! App: file:///Users/me/Library/Group%20Containers/ SysExt: file:///private/var/root/Library/Group%20Containers/
4
1
1.4k
1d
Transparent proxy breaks apps on macOS 15.7.8 RC 5
Hello! Users of my app observed behaviour that some apps stopped working after update to 15.7.8 via Beta channel with transparent proxy network extension on. The app receives Protocol not available error, and I see setsockopt SO_FLOW_DIVERT_TOKEN failed [42: Protocol not available] error in Console. To reproduce, create two rules in basic NETransparentProxyProvider: [[NENetworkRule alloc] initWithDestinationNetwork:nil prefix:0 protocol:NENetworkRuleProtocolTCP], [[NENetworkRule alloc] initWithDestinationNetwork:nil prefix:0 protocol:NENetworkRuleProtocolUDP], You may even return NO in handleNewFlow, it does not matter. After that, Safari won't open some sites, and Weather app will work unreliably. Do anyone knows any workaround for this problem? I've also create a relevant FB23788740.
8
0
1.1k
1d
nesessionmanager infinite retry loop causes permanent, unfixable Local Network Access denial (System Settings UI misrepresents actual enforcement state)
Summary On macOS 26.6.2 (25G83) (Tahoe), Chrome fails to load pages hosted on private/local IP addresses (e.g. http://192.168.0.43/, a home AV receiver's web setup page) with ERR_ADDRESS_UNREACHABLE, while Safari loads the identical URL without issue on the same Mac, same network. ping to the target IP succeeds normally. System Settings > Privacy & Security > Local Network shows "Allow" for Chrome — but this is not what's actually being enforced. Traced the root cause to nesessionmanager stuck in an infinite retry loop when attempting to install/update NetworkExtension path-rule policies. Because the daemon never completes a successful policy install, nehelper (the actual enforcement point) continues serving a stale cached "denied" decision indefinitely, regardless of what the Settings UI shows or how many times the toggle is flipped. Key evidence Chrome netlog (chrome://net-export/) shows a genuine TCP connect attempt (not an early permission rejection): text TCP_CONNECT_ATTEMPT --> address = "192.168.0.43:80" -TCP_CONNECT_ATTEMPT --> os_error = 65 -TCP_CONNECT --> net_error = -109 (ERR_ADDRESS_UNREACHABLE) os_error = 65 is BSD EHOSTUNREACH, returned by the kernel at the connect() syscall. Unified log (log stream --predicate 'subsystem == "com.apple.networkextension"') shows the actual enforcement decision: text nehelper: UUID cache hit for com.google.Chrome nehelper: Local network denied by preference for Google Chrome (com.google.Chrome) This reproduced identically across multiple attempts, minutes apart, and survived sudo pkill -f nehelper — confirming the decision is persisted, not just an in-memory cache. While toggling the Local Network switch in System Settings, expecting a pathRules dump, the log instead showed: text NESMPathControllerSession[...]: No UUIDs in the cache for PathRuleDefaultNonSystemIdentifier, populating the cache from the path rules NESMPathControllerSession[...]: Will reinstall policies after 2000 milliseconds, retry 1 NESMPathControllerSession[...]: Will reinstall policies after 2000 milliseconds, retry 2 NESMPathControllerSession[...]: Will reinstall policies after 2000 milliseconds, retry 3 NESMPathControllerSession[...]: Will reinstall policies after 2000 milliseconds, retry 4 NESMPathControllerSession[...]: Will reinstall policies after 2000 milliseconds, retry 5 The retry counter kept incrementing with no observed successful completion, meaning any change made via the System Settings toggle can never actually propagate to nehelper. Also confirmed Local Network privacy is not TCC-backed at all: text $ sudo tccutil reset LocalNetwork tccutil: Failed to reset LocalNetwork $ sudo tccutil reset LocalNetwork com.google.Chrome tccutil: Failed to reset LocalNetwork approval status for com.google.Chrome Both fail outright rather than erroring on bad usage, confirming this permission lives entirely in the NetworkExtension path-rule system, with no supported reset command. What did not fix it Toggling the Settings UI switch off/on sudo tccutil reset SystemPolicyNetworkVolumes (wrong TCC service — doesn't apply here) sudo tccutil reset LocalNetwork (fails, see above) Full clean reinstall of Chrome, including all Application Support/Caches/Preferences Safe Mode boot sudo pkill -f nehelper Full normal system restart Launching Chrome with --no-sandbox (rules out Chromium's own internal sandbox as a factor — this is a system-level enforcement, not a Chromium-side block) Working fix Requires temporarily disabling SIP: text In macOS Recovery (csrutil only works from Recovery, not Safe Mode): csrutil disable Back in normal macOS: sudo rm /Library/Preferences/com.apple.networkextension.plist Back in Recovery: csrutil enable Restart normally. nesessionmanager rebuilds the NetworkExtension policy state from scratch on next boot. System Settings > Privacy & Security > Local Network shows a clean list afterward (also clears the separate, previously-known bug where every Chrome auto-update was creating a new duplicate entry in that list rather than updating the existing one). Chrome's next navigation attempt to a local IP correctly triggers a fresh permission prompt, and functions normally once granted. Why this matters Apple's own TN3179 states there's no supported way to reset an app's Local Network privilege to "undetermined." This bug compounds that: once nesessionmanager gets stuck in this retry loop, there is no path back to a working state short of disabling SIP and manually deleting a system preference file. The Settings UI also actively misrepresents the true enforcement state (shows "Allow" while nehelper enforces "denied") with no indication to the user that anything is wrong. This is likely not Chrome-specific — any app relying on Local Network access (smart-home apps, casting/streaming apps, IoT config tools) would hit the identical wall once a Mac's nesessionmanager enters this state. Also filed via Feedback Assistant. Happy to share the full decoded netlog trace if useful — didn't attach it here to keep this post scannable, but can paste the additional detail on request.
2
1
609
2d
NEURL Filter configuration approved under wrong Developer Team — resubmission blocked by duplicate domain
Hi, My NEURL Filter configuration (ID 9f3cbff8-63de-4c69-bf68-c19cd1c5d842) was approved on August 5, 2026, but it turned out to be attached to the wrong Apple Developer Team — an old individual account (S5VDH23BBZ) I no longer have access to, instead of my actual organization team, KRKJ76BC7W (SCOTTO), which owns and signs the app (bundle ID com.dropbet.DropBet). I contacted Developer Support, who said they couldn't transfer it and suggested replying to the original approval email — I did (on 08/08, then several times) with no response. I then tried resubmitting the same configuration under the correct team (KRKJ76BC7W), but the portal rejected it with: "A configuration with the same PIR Server Domain already exists." So a fresh submission is technically impossible while the original stays attached to the wrong account — the only real fix is transferring or re-attaching the existing approved configuration to KRKJ76BC7W. Has anyone dealt with this kind of Team ID mix-up before, or know who on the Network Extension / NEURL Filter team could help reassign an approved configuration? Happy to provide any additional details. Thanks in advance.
13
1
1.9k
5d
Does "Connectivity Assist" bypass NEPacketTunnelProvider DNS interception on iOS 27?
We have a NEPacketTunnelProvider extension that intercepts and modifies DNS responses for specific hostnames as part of its normal operation. On iOS 27, we are seeing this interception being intermittently bypassed. Our extension still receives the DNS query, builds a response, and returns it promptly, but the client occasionally proceeds using a different address, presumably the actual DNS resolution result. This behavior does not reproduce on iOS 26 or earlier. The timing in our logs appears to correlate with the new Connectivity Assist feature (Settings → Wi-Fi), which Apple describes as using cellular data alongside Wi-Fi to improve reliability. Our suspicion is that Connectivity Assist may be performing DNS resolution over a cellular path in parallel, outside the tunnel, causing that resolution path to bypass our provider entirely. We have ruled out response timing and response format issues on our side. Varying the speed and format of our responses does not affect the outcome, suggesting that the behavior is occurring at a layer above the tunnel provider. We have the following questions: Does Connectivity Assist perform DNS resolution on a network path that can bypass an active NEPacketTunnelProvider? Is there any API, entitlement, or supported mechanism to disable Connectivity Assist for an app, or to ensure that all DNS resolution is routed through the active tunnel, similar to previous Wi-Fi Assist opt-out capabilities? Would a NEDNSProxyProvider-based DNS proxy be affected in the same way, or does it operate at a layer that Connectivity Assist cannot bypass? Any guidance, references to relevant documentation, WWDC session content, or confirmation of the expected behavior would be greatly appreciated.
5
0
546
6d
IKEv2 Personal VPN: Child SA torn down after 120s idle (NEIKEv2ErrorDomain Code=15) with DisconnectOnIdle already NO
We ship a consumer VPN app on iOS, iPadOS and tvOS using a Personal VPN configuration: NEVPNManager with NEVPNProtocolIKEv2, EAP-MSCHAPv2, no MDM profile installed. After exactly 120 seconds with no traffic, iOS destroys the Child SA and disconnects the tunnel. I would like to know whether that timer is configurable, and if not, what the intended mitigation is. From a device sysdiagnose (iPhone, iOS 26.6, build 23G71): NEIPSecDBStatsUpdate: SA is idle for past 120 secs KernelSASession[1, IKEv2 Session Database] idle timeout SA Internal SAID = 2 SPI = C53320D1 Direction = Outbound ChildSA[1] state Connected -> Disconnected error Domain=NEIKEv2ErrorDomain Code=15 "IdleTimeout" <NEIKEv2Provider: Primary Tunnel>: stopping tunnel since Child disconnected nesessionmanager: plugin disconnected with reason "Tunnel was idle for too long" This happened 12 times across a 9.2 hour overnight capture on one idle device. Median time before the tunnel re-established was 14m35s. DISCONNECT-ON-IDLE IS NOT ENABLED The same sysdiagnose shows the plugin's own configuration as: disconnectOnIdle = NO disconnectOnIdleTimeout = 0 These are the stock defaults. There is no disconnectOnIdle property on the public NEVPNProtocol, so a Personal VPN app cannot set them either way. The installed SA parameters contain no idle field at all, only "Lifetime Seconds = 1800", which is honoured correctly: the same capture shows 20 clean rekey cycles. WHAT I HAVE RULED OUT iOS logs a distinct stop reason for each of the following, and none of them occurred across 24 teardowns: "On Demand Disconnect rule matched", "Tunnel was terminated by the server", "Server is not responding", "Network changed, tunnel no longer viable", "Device went to sleep", "Stop command received". The only reason recorded was "Tunnel was idle for too long". On the gateway (strongSwan), IKE rekey and reauth are disabled, uniqueids is never, and DPD is answered in roughly 200ms right up to the teardown. The gateway considers the tunnel healthy at the moment iOS tears it down. DPD does not reset the timer, which makes sense: DPD is an INFORMATIONAL exchange on the IKE SA, whereas the log shows the timer measuring the OUTBOUND Child SA (SAID 2). THE APP CANNOT SEE THIS HAPPEN NEVPNConnection.fetchLastDisconnectError() returns nil for this teardown, because the error is in NEIKEv2ErrorDomain rather than NEVPNConnectionErrorDomain. The app has no supported way to detect that the tunnel dropped for this reason, or to distinguish it from a user-initiated disconnect. It is only visible in a sysdiagnose. TRAFFIC IS NOT HELD DURING THE RECONNECT With Connect On Demand enabled (NEOnDemandRuleConnect, interfaceTypeMatch .any), traffic after the teardown does not wait for the tunnel. On device wake following one of these drops: 01:32:43 device wakes 01:32:43 [C331 ... :443] path:satisfied @0.001s, interface: en0[802.11], uses wifi 01:32:44 [C331 ... :443] flow:finish_connect @0.623s (over en0) 01:32:47 tunnel status changed to connected so flows complete over the physical interface for several seconds before the VPN re-establishes. On-demand triggered the reconnect but did not delay the traffic. For a VPN product this window is the part that concerns me most. QUESTIONS Is the 120 second Child SA idle timeout configurable for a Personal VPN using NEVPNProtocolIKEv2, from the app or from a configuration profile? If DisconnectOnIdle / DisconnectOnIdleTimer are meant to control it, why does the teardown occur when they are NO / 0? If it is not configurable, is application-generated keepalive traffic the intended workaround? If so, how is that expected to work while iOS has the app suspended, which is exactly when a tunnel goes idle? Would server originated traffic that elicits a client response be a supported approach? Is there any supported way for an app to be notified of this teardown, given that fetchLastDisconnectError() returns nil for it? Is the behaviour in "traffic is not held during the reconnect" expected for NEOnDemandRuleConnect, or should matching flows be delayed until the tunnel is up? Is includeAllNetworks the only supported way to close that window? Happy to supply the full sysdiagnose privately.
3
0
408
1w
Best practices for parallelizing handleNewFlow(_:) or async verdicts in NETransparentProxyProvider
Hello, I am working on network traffic analyser for DLP. The solution is based on NETransparentProxyProvider due to necessity data modifying. To my knowledge, the Transparent Proxy API does not have a concept of an "asynchronous/delayed verdict" for flow authorization. The handleNewFlow(_:) method appears to be invoked sequentially for each new network flow that matches our interception rules. The challenge we are facing is that a portion of our proxy rules and configurations resides in a LaunchDaemon, which the Network Extension communicates with via XPC. Such separation is required by our cross-platform code base. Thus, we are forced to make a synchronous XPC call within handleNewFlow(_:) and block the thread while waiting for the interception verdict from the daemon. This creates a significant bottleneck when establishing new network flows. The current implementation works without issues, but we are always looking for ways to improve performance. Are there any best practices or recommended approaches for parallelizing handleNewFlow(_:)? Thank you in advance.
2
0
213
1w
Memory leak in CFNetwork (PACClient/PACQuery) when using NETransparentProxyProvider with Auto Proxy Discovery enabled
Hello, I have encountered unexpected behavior when running a Network Extension that implements NETransparentProxyProvider. This extension is part of a DLP (Data Loss Prevention) solution. If the "Auto proxy discovery" option is enabled for the Wi-Fi connection on the managed host, the leaks tool reports memory leaks with the following root cycles: ... 11 (1.03K) ROOT CYCLE: <CFRunLoopSource 0xa430cc540> [192] 10 (864 bytes) ROOT CYCLE: <std::__shared_ptr_emplace<PAC::PACClient> 0xa430f4000> [224] CYCLE BACK TO <CFRunLoopSource 0xa430cc540> [192] 6 (400 bytes) ROOT CYCLE: <std::__shared_ptr_emplace<PAC::PACQuery> 0xa43118080> [128] 2 (80 bytes) ROOT CYCLE: 0xa42ca9000 [32] 1 (48 bytes) ROOT CYCLE: <__NSMallocBlock__ 0xa42804de0> [48] CFNetwork invocation function for block in PAC::PACClient::initialize(void const*, __CFURL cons..." 1 (32 bytes) ROOT CYCLE: <std::__shared_ptr_pointer<BlockHolderVar<__CFString const*, __CFData const*, std::shared_ptr<__CFArray const>, std::shared_ptr<__CFError>>*, SmartBlockWithArgs<__CFString const*, __CFData const*, std::shared_ptr<__CFArray const>, std::shared_ptr<__CFError>>::Deleter> 0xa4343da80> [32] 2 (160 bytes) <NSURL 0xa42840310> [112] 1 (48 bytes) _clients --> <CFString 0xa42c08fc0> [48] 1 (160 bytes) <NWConcrete_nw_pac_resolver 0xa4280cb40> [160] 1 (48 bytes) <CFError 0xa42804ed0> [48] 1 (32 bytes) <std::__shared_ptr_pointer<__CFError*, Deleter_CFRelease> 0xa4343dc20> [32] ... 11 (1.03K) ROOT CYCLE: <CFRunLoopSource 0xa43128540> [192] 10 (864 bytes) ROOT CYCLE: <std::__shared_ptr_emplace<PAC::PACClient> 0xa430f4a80> [224] CYCLE BACK TO <CFRunLoopSource 0xa43128540> [192] 6 (400 bytes) ROOT CYCLE: <std::__shared_ptr_emplace<PAC::PACQuery> 0xa43118880> [128] 2 (80 bytes) ROOT CYCLE: 0xa428105e0 [32] 1 (48 bytes) ROOT CYCLE: <__NSMallocBlock__ 0xa428887b0> [48] CFNetwork invocation function for block in PAC::PACClient::initialize(void const*, __CFURL cons..." 1 (32 bytes) ROOT CYCLE: <std::__shared_ptr_pointer<BlockHolderVar<__CFString const*, __CFData const*, std::shared_ptr<__CFArray const>, std::shared_ptr<__CFError>>*, SmartBlockWithArgs<__CFString const*, __CFData const*, std::shared_ptr<__CFArray const>, std::shared_ptr<__CFError>>::Deleter> 0xa4343f5c0> [32] 2 (160 bytes) <NSURL 0xa428424c0> [112] 1 (48 bytes) _clients --> <CFString 0xa42c0a640> [48] 1 (160 bytes) <NWConcrete_nw_pac_resolver 0xa4280d7c0> [160] 1 (48 bytes) <CFError 0xa42888390> [48] 1 (32 bytes) <std::__shared_ptr_pointer<__CFError*, Deleter_CFRelease> 0xa4343f4c0> [32] ... The extension creates an nw_connection_t to the remote host for each handled flow like this: nw_parameters_t parameters = nw_parameters_create_secure_tcp(NW_PARAMETERS_DISABLE_PROTOCOL, NW_PARAMETERS_DEFAULT_CONFIGURATION); nw_endpoint_t connectTo = nw_endpoint_create_host([endpoint.hostname UTF8String], [endpoint.port UTF8String]); nw_connection_t connection = nw_connection_create(connectTo, parameters); When "Auto proxy discovery" is disabled, everything works as expected, and no memory leaks or issues are observed. Could you please advise on how to resolve or work around this issue? Thank you in advance!
2
0
529
2w
Potential Network Extension memory leak
I've been investigating memory consumption issues with a network extension. It seems as though the "leak" exists within the network extension framework. Looking at a memory graph, there appears to be a _socketFlows dictionary or similar in the network extension framework that holds all of the flows and they continue to pile up and consume memory. The problem seems to be particularly bad when I leave my MacBook plugged in and running over the weekend and come back on Monday. For instance, in that time period, the memory consumption grew from <100MB to >1GB. To try to narrow this down to see if it was an "us" problem or truly in the framework, I installed Objective-See Foundation's Lulu tool, which also uses a network extension. It similarly saw memory grow from around 30MB or less to 600MB in the same time frame. Has anyone else seen this? We do have an open feedback ticket FB18731867 that might be related that has to do with seeing multiple instances of the same data filter provider started and maybe running in a network extension.
5
0
791
2w
NEPacketTunnelProvider for local device wide bandwidth pacing without a remote VPN server
We are developing a consumer digital wellbeing app and are evaluating an optional feature that deliberately slows network traffic when explicitly enabled by the user. The proposed architecture is: NEPacketTunnelProvider device wide traffic no MDM or per-app VPN no remote VPN server packet processing/forwarding performed locally on device no application layer inspection no traffic profiling or advertising use no network data uploaded to our servers bounded latency/bandwidth shaping only user can disable it immediately The goal is user controlled “intentional bandwidth” rather than security, privacy filtering, circumvention, or remote VPN access. I’d like to clarify: Is NEPacketTunnelProvider an appropriate public API for this kind of local device wide bandwidth pacing? Is it supported to consume packets via packetFlow, process/forward them locally, and send them to the public Internet without a remote VPN endpoint? Is intentional bandwidth/latency shaping considered an acceptable use of the Network Extension entitlement? Would this still be treated as a “VPN service” for App Review purposes even though no traffic is routed through a remote VPN service? If so, would the organization-enrollment requirement for VPN apps apply? Is there another public API Apple recommends for this use case? I understand that the App Review questions may ultimately need to be answered by App Review rather than the forums. My main goal here is to validate whether this is a supported NetworkExtension architecture before investing in the packet processing implementation.
1
0
213
2w
NEURLFilter & Live Caller ID Lookup approved, but PIR token issuer directory returns 404 on device — filter never activates (serverSetupIncomplete)
Our NEURLFilter (URL Filter) and Live Caller ID Lookup entitlements are both Approved, but on device the URL filter never activates — the NEURLFilter session loops starting → stopped, so no URLs are filtered. App: bundle app.scamzero (App ID 6768433709) URL Filter Config ID: 224e33c9-b03b-44ce-87cd-2153c86908ce Live Caller ID Lookup Config ID: 0abdc42b-c98b-4753-8af4-9334d42abdb8 Device: iPhone on iOS 26. Test account has the paid plan that enables the feature. The system log shows the failure is fetching the Privacy Pass token issuer directory through Apple's relay (gateway.icloud.com), not our backend: ciphermld : Failed to fetch Token Issuer Directory. URL: https://gateway.icloud.com/pat-issuer-directory?issuer=issuer.scamzero.app Status Code: 404 → NEURLFilterPlugin setStatus:error — NEAgentURLFilterErrorDomain Code=3 → NESMURLFilterSession status changed to stopping with error 3 → prefilterCleanup (removes the prefilter mmap) → session restarts → loop This looks like NEURLFilterManager.Error.serverSetupIncomplete (9). What we have already verified on our side (everything conforms): We run Apple's unmodified reference PIRService (from apple/pir-service-example). Our issuer directory at https://issuer.scamzero.app/.well-known/private-token-issuer-directory returns 200 with valid token-keys (token-type 2) and issuer-request-uri. OHTTP gateway (gateway.scamzero.app/ohttp-configs and /gateway), PIR service (service.scamzero.app), use case app.scamzero.url.filtering, bearer token AAAA, DNS TXT apple-url-filter=app.scamzero, and the validation URL apple.com/url-filter-test = 1 are all configured. Our pre-launch checks pass. The precomputed Bloom prefilter loads on device with the correct bit/hash/seed and ~37k domains. Key observation: our OHTTP gateway logs show zero successful OHTTP requests from Apple's infrastructure since Aug 10, and gateway.icloud.com/pat-issuer-directory?issuer=issuer.scamzero.app returns 404 on device. This suggests our Privacy Pass issuer is not registered/validated in Apple's PAT issuer directory, even though both entitlements are Approved. Question: How can we get our Privacy Pass issuer (issuer.scamzero.app) registered/validated in Apple's token issuer directory for the use cases app.scamzero.url.filtering and Live Caller ID Lookup? The entitlements are approved, but the on-device token issuer directory lookup fails with 404, which prevents the filter from ever activating. Happy to file a Feedback with a sysdiagnose if useful.
1
0
325
3w
SecKeychainGetStatus returns varying errors after SecKeychainOpen succeeds on macOS 26.6.2
Hi, so far i've been using the following API Call Sequence in order to check the keychain status prior to using one of the certificates inside the keychain for signing or verifying remote tls connection. // Step 1: Open keychain SecKeychainRef keychain = nil; OSStatus status = SecKeychainOpen(keychainPath, &keychain); // Result: errSecSuccess (0) — always succeeds, returns valid ref // Step 2: Get keychain status SecKeychainStatus keychainStatus = 0; status = SecKeychainGetStatus(keychain, &keychainStatus); // Result: FAILS : see table below I observed SecKeychainGetStatus return errSecInternalError or errSecInvalidHandle │ Process │ Keychain │ Error │ Code │ │ GUI App │ login.keychain. | errSecInternalError │ -26276 │ │ GUI App │ login.keychain | errSecInvalidHandle │ -25308 │ │ GUI App │ /Library/Keychains/System.keychain | errSecInvalidHandle │ -25308 │ │ Daemon │ /Library/Keychains/System.keychain │ errSecInvalidHandle │ -25308 │ SecKeychainOpen always returns errSecSuccess with a non-null SecKeychainRef SecKeychainGetStatus fails immediately when called on that reference The error for login.keychain varies between calls (not deterministic) The error for System.keychain is consistently errSecInvalidHandle Issue is 100% reproducible. every keychain access attempt fails Started immediately after macOS 26.6.2 upgrade Do you know if there were any changes in 26.6.2 that could have caused this behavior? If this is expected, how should I address it? Thanks
1
0
246
3w
IncludeAllNetworks with excludeRoutes do not seem to work together
When I set IncludeAllNetworks in my VPN profile along with the excludeRoutes for traffic I want to go outside of the tunnel, I see the exclude routes being added to the the route table but when we try and to connect on those hosts something is setting a route to the TUN interface and ignoring the excluded routes in the settings. I'm assuming that some policy before any route table is being looked at is doing this but does that then mean IncludeAllNetworks=true and excludeRoutes are actually incompatible? Is this documented and I just missed it?
2
0
205
3w
NEURLFilterControlProvider: stop(reason:) always receives .none, and is skipped in some teardown paths
Testing NEURLFilterControlProvider on iOS 26.6, I've measured two behaviours around stop(reason:) that I can't reconcile with the documentation, and I'd like to know whether either is intended. Setup: development-signed build, filter configured and running, instrumented stop(reason:) that logs the reason and then emits elapsed-time markers. All timings below are from device log archives collected with "log collect --device". The reason parameter is always .none nesessionmanager records a specific stop reason for itself, then passes .none (0) to the provider. Two different user actions, two different internal reasons, same .none delivered. Disabling via NEURLFilterManager (isEnabled = false, saveToPreferences): 15:20:12.173 nesessionmanager: Setting last stop reason to 31 15:20:12.175 MyProvider: stop() entered, reason=0 (none) 15:20:14.208 MyProvider: stop() completed after 2032ms 15:20:14.209 nesessionmanager: status changed to disconnected, last stop reason Configuration was disabled Disabling the filter from iOS Settings: 15:25:04.167 nesessionmanager: Setting last stop reason to 1 15:25:04.169 MyProvider: stop() entered, reason=0 (none) 15:25:06.198 MyProvider: stop() completed after 2028ms 15:25:06.200 nesessionmanager: status changed to disconnected, last stop reason Stop command received The system clearly knows why the filter stopped, and the values it uses (31, 1) are outside the NEProviderStopReason range, so there appears to be a richer internal enum that isn't mapped through to the provider. Is .none what a URL filter control provider should expect in all cases? If so, is there any supported way for the provider to distinguish a deliberate user disable from system settings, say, an in-app disable, app update or a configuration reload? Is stop(reason:) guaranteed to be called? I do cleanup work in stop(), so I want to know whether it is a reliable lifecycle callback or a best-effort one. I found at least one teardown path where it is skipped entirely. When the configuration is removed as part of the app being deleted, the session is torn down without the provider ever being invoked. The extension is terminated roughly 355 ms before the configuration removal: 15:33:59.361 launchd: service inactive: MyProvider, leaving extension group 15:33:59.363 runningboardd: Executing termination request 15:33:59.431 launchd: remove all extension instances ... total of 0 extension instances were found to remove 15:33:59.716 nesessionmanager: configuration has been removed, stopping 15:33:59.716 nesessionmanager: Setting last stop reason to 32 15:33:59.717 nesessionmanager: plugin NEURLFilterPlugin(...) disposed 15:33:59.717 nesessionmanager: status changed to disconnected, last stop reason Configuration was removed No stop() call appears anywhere in the archive, and the plugin is disposed rather than stopped. The whole teardown takes one millisecond. The ordering looks like the cause: by the time nesessionmanager removes the configuration, the extension process is already gone, so there is nothing left to call stop() on. Thread 713975 describes stopTunnel being called with .providerDisabled in the equivalent situation for a packet tunnel provider, so the behaviour appears to differ by provider type. So my question is about the contract rather than this specific path: should a URL filter control provider treat stop(reason:) as guaranteed, or as best-effort? If it is best-effort, are there other teardown paths where it is skipped, so I can make sure nothing important depends on it running?
1
0
453
3w
Can Multiple Transparent Proxies Work Simultaneously on macOS?
Hi everyone, I’m looking for some clarification regarding "Transparent Proxy configurations on macOS". We have a requirement where multiple security/networking solutions may need to use a "Transparent Proxy" on the same macOS device. For example, one solution may be used for web security/SSL inspection while another solution is required for a different security or network traffic use case. My questions are: 1.Does macOS support configuring and running multiple Transparent Proxy providers simultaneously on the same device? 2. If multiple Transparent Proxy configurations are deployed through MDM, how does macOS determine which proxy handles the traffic? 3.Is there any limitation on the number of Transparent Proxy configurations/providers that can be configured? 4.Can multiple Transparent Proxy providers coexist if they handle different traffic, applications, or network interfaces? 5. If multiple providers are configured, is there a supported mechanism to define priority, ordering, or traffic routing between them? 6. Are there any differences or limitations across recent macOS versions, particularly macOS 15 and macOS 26? 7. Is there any Apple documentation or recommended architecture for deploying multiple Transparent Proxy providers on the same Mac? If anyone has implemented or tested a setup with multiple Transparent Proxy providers on macOS, I’d appreciate any insights, documentation, or real-world experience. Thanks! Vijay
1
0
373
3w
Network Extension Resources
General: Forums subtopic: App & System Services > Networking DevForums tag: Network Extension Network Extension framework documentation Routing your VPN network traffic article Filtering Network Traffic sample code TN3120 Expected use cases for Network Extension packet tunnel providers technote TN3134 Network Extension provider deployment technote TN3165 Packet Filter is not API technote Network Extension and VPN Glossary forums post Debugging a Network Extension Provider forums post Exporting a Developer ID Network Extension forums post Network Extension Framework Entitlements forums post Network Extension vs ad hoc techniques on macOS forums post Network Extension Provider Packaging forums post NWEndpoint History and Advice forums post Extra-ordinary Networking forums post URL filter: WWDC 2025 Session 234 Filter and tunnel network traffic with NetworkExtension URL filters documentation Filtering traffic by URL sample code Setting up a PIR server for URL filtering sample code Using the Bloom filter tool to configure a URL filter sample code PIR Service Example open source server sample and specifically its documentation Wi-Fi management: Understanding NEHotspotConfigurationErrorInternal forums post See also Networking Resources for general networking resources, including information about Wi-Fi. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com"
Replies
0
Boosts
0
Views
4.1k
Activity
Aug ’26
sysextd: "no policy, cannot allow apps outside /Applications" - NEFilterDataProvider system extension on macOS 26
I'm developing a macOS security tool using NEFilterDataProvider as a system extension. On macOS 26 beta (25E241), sysextd consistently rejects my extension with: sysextd: no policy, cannot allow apps outside /Applications Configuration: App installed in /Applications/ Signed with Developer ID Application (693DSH8GN5) Entitlement: com.apple.developer.networking.networkextension = content-filter-provider com.apple.developer.system-extension.install = true Developer Mode enabled on test machine Comparison with Little Snitch: Little Snitch runs correctly on the same machine. Key differences I found: Little Snitch uses content-filter-provider-systemextension instead of content-filter-provider Little Snitch has com.apple.security.app-sandbox = false Both signed with Developer ID Application When I switch to content-filter-provider-systemextension, Xcode rejects every provisioning profile because none match that entitlement value, and the Developer Portal doesn't expose fine-grained control over the Network Extensions array values. Questions Is content-filter-provider-systemextension the correct entitlement for system extensions on macOS 26? How should the provisioning profile be configured to support it? Is there a known sysextd issue on macOS 26 beta causing this regardless of configuration? Is there - somewhere! - a guide on how to build such an extension? Thanks in advance for your help.
Replies
4
Boosts
0
Views
702
Activity
19h
NEURLFilter / SimpleURLFilter: neagent fails to open URL prefilter mmap file with errno 13 Permission denied
I am testing NEURLFilter on macOS using the SimpleURLFilter sample, and I am seeing a failure from neagent while it is saving the local URL prefilter Bloom filter to its mmap file. The relevant log is: neagent +[NEBloomFilter mmapToFile:data:dataLength:numberOfBits:numberOfHashes:murmurSeed:tag:]: NEBloomFilter - failed to open mmap file /private/var/db/urlPrefilter/com.apple.networkextension.url-prefilter-data.temp.com.example.apple-samplecode.SimpleURLFilterTC3Q7MAJXF <errno 13 - Permission denied> neagent <NEAgentURLFilterExtension: 0xc8ce64280>: -[NEAgentURLFilterExtension startURLFilter]_block_invoke - Failed to save first fetch of pre-filter data Environment: macOS: 26.5.1 (25F80) Xcode: 26.5 (17F42) Platform: macOS Signing type: Apple Development (automatically manage signing) What I am doing: Build and run the containing app. Save and enable the NEURLFilterManager configuration. The URL filter provider starts. The provider's prefilter code is reached. neagent logs the mmap failure above while trying to open a temporary file under /private/var/db/urlPrefilter. Expected result: neagent should be able to create or open its system-managed URL prefilter cache / mmap file under /private/var/db/urlPrefilter, and the local Bloom filter should be loaded successfully. Actual result: neagent fails to open the temporary mmap file with errno 13 Permission denied: /private/var/db/urlPrefilter/com.apple.networkextension.url-prefilter-data.temp.<bundle/team-specific suffix> I am not manually creating, modifying, or chmod/chown-ing /private/var/db/urlPrefilter or anything inside it. The directory and its contents are entirely system-managed. The failure appears to happen inside neagent while it is handling the system-managed URL prefilter cache. The failure occurs at the mmapToFile: step while neagent saves the Bloom filter prefilter data. Directory state: drwxr-xr-x 2 root wheel 64 /private/var/db/urlPrefilter Has anyone else encountered this? Any suggestions on what could cause neagent to fail with errno 13 on its own mmap file under /private/var/db/urlPrefilter?
Replies
9
Boosts
2
Views
1.1k
Activity
20h
Appropriate API for measuring device-wide network traffic on iOS
I am developing a consumer iOS app for App Store distribution and would like to confirm the appropriate public API for the following use case. The app needs to measure the amount of network traffic passing through the device over short time intervals, for example once per second or more frequently. The app does not need to inspect packet contents, block or filter traffic, or provide a remote VPN service. It also should not generate dedicated network traffic solely for speed measurement. The goal is only to observe device-wide network traffic volume and convert that information into a simple real-time indicator for the user. I am currently investigating the Network Extension framework. Would NEPacketTunnelProvider be an appropriate API for this use case? If not, is there another supported Network Extension provider or other public iOS API intended for measuring device-wide network traffic in this manner? I would like to choose an architecture that is technically supported by Apple and appropriate for a consumer app distributed through the App Store before beginning implementation.
Replies
0
Boosts
0
Views
27
Activity
20h
Read file with System Network Extension from App Group
I have trouble with reading a file from an App Group with my System Network Extension. The app group container is found successfully. However the file read returns empty. In the app itself the same code runs fine and returns a string array of items found in the file. Code: func readFile() - [String] {         var jsonResult: [String] = []         guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: AppConstants.groupID) else {             fatalError()         }         let fileURL = containerURL.appendingPathComponent("file.json")         if let data = try? NSData(contentsOfFile: fileURL.path, options: .mappedIfSafe) as Data {             if let json = try? JSONSerialization.jsonObject(with: data, options: .fragmentsAllowed) {                 jsonResult = json as! [String]             }         }         os_log("jsonResult: %{public}@", jsonResult)         return jsonResult     } Log: default 09:42:19.486793+0200 app-network-extension container_create_or_lookup_app_group_path_by_app_group_ identifier: success default 09:42:20.105792+0200 app-network-extension jsonResult: ( ) Edit, after more digging: fileURL is different! App: file:///Users/me/Library/Group%20Containers/ SysExt: file:///private/var/root/Library/Group%20Containers/
Replies
4
Boosts
1
Views
1.4k
Activity
1d
Transparent proxy breaks apps on macOS 15.7.8 RC 5
Hello! Users of my app observed behaviour that some apps stopped working after update to 15.7.8 via Beta channel with transparent proxy network extension on. The app receives Protocol not available error, and I see setsockopt SO_FLOW_DIVERT_TOKEN failed [42: Protocol not available] error in Console. To reproduce, create two rules in basic NETransparentProxyProvider: [[NENetworkRule alloc] initWithDestinationNetwork:nil prefix:0 protocol:NENetworkRuleProtocolTCP], [[NENetworkRule alloc] initWithDestinationNetwork:nil prefix:0 protocol:NENetworkRuleProtocolUDP], You may even return NO in handleNewFlow, it does not matter. After that, Safari won't open some sites, and Weather app will work unreliably. Do anyone knows any workaround for this problem? I've also create a relevant FB23788740.
Replies
8
Boosts
0
Views
1.1k
Activity
1d
nesessionmanager infinite retry loop causes permanent, unfixable Local Network Access denial (System Settings UI misrepresents actual enforcement state)
Summary On macOS 26.6.2 (25G83) (Tahoe), Chrome fails to load pages hosted on private/local IP addresses (e.g. http://192.168.0.43/, a home AV receiver's web setup page) with ERR_ADDRESS_UNREACHABLE, while Safari loads the identical URL without issue on the same Mac, same network. ping to the target IP succeeds normally. System Settings > Privacy & Security > Local Network shows "Allow" for Chrome — but this is not what's actually being enforced. Traced the root cause to nesessionmanager stuck in an infinite retry loop when attempting to install/update NetworkExtension path-rule policies. Because the daemon never completes a successful policy install, nehelper (the actual enforcement point) continues serving a stale cached "denied" decision indefinitely, regardless of what the Settings UI shows or how many times the toggle is flipped. Key evidence Chrome netlog (chrome://net-export/) shows a genuine TCP connect attempt (not an early permission rejection): text TCP_CONNECT_ATTEMPT --> address = "192.168.0.43:80" -TCP_CONNECT_ATTEMPT --> os_error = 65 -TCP_CONNECT --> net_error = -109 (ERR_ADDRESS_UNREACHABLE) os_error = 65 is BSD EHOSTUNREACH, returned by the kernel at the connect() syscall. Unified log (log stream --predicate 'subsystem == "com.apple.networkextension"') shows the actual enforcement decision: text nehelper: UUID cache hit for com.google.Chrome nehelper: Local network denied by preference for Google Chrome (com.google.Chrome) This reproduced identically across multiple attempts, minutes apart, and survived sudo pkill -f nehelper — confirming the decision is persisted, not just an in-memory cache. While toggling the Local Network switch in System Settings, expecting a pathRules dump, the log instead showed: text NESMPathControllerSession[...]: No UUIDs in the cache for PathRuleDefaultNonSystemIdentifier, populating the cache from the path rules NESMPathControllerSession[...]: Will reinstall policies after 2000 milliseconds, retry 1 NESMPathControllerSession[...]: Will reinstall policies after 2000 milliseconds, retry 2 NESMPathControllerSession[...]: Will reinstall policies after 2000 milliseconds, retry 3 NESMPathControllerSession[...]: Will reinstall policies after 2000 milliseconds, retry 4 NESMPathControllerSession[...]: Will reinstall policies after 2000 milliseconds, retry 5 The retry counter kept incrementing with no observed successful completion, meaning any change made via the System Settings toggle can never actually propagate to nehelper. Also confirmed Local Network privacy is not TCC-backed at all: text $ sudo tccutil reset LocalNetwork tccutil: Failed to reset LocalNetwork $ sudo tccutil reset LocalNetwork com.google.Chrome tccutil: Failed to reset LocalNetwork approval status for com.google.Chrome Both fail outright rather than erroring on bad usage, confirming this permission lives entirely in the NetworkExtension path-rule system, with no supported reset command. What did not fix it Toggling the Settings UI switch off/on sudo tccutil reset SystemPolicyNetworkVolumes (wrong TCC service — doesn't apply here) sudo tccutil reset LocalNetwork (fails, see above) Full clean reinstall of Chrome, including all Application Support/Caches/Preferences Safe Mode boot sudo pkill -f nehelper Full normal system restart Launching Chrome with --no-sandbox (rules out Chromium's own internal sandbox as a factor — this is a system-level enforcement, not a Chromium-side block) Working fix Requires temporarily disabling SIP: text In macOS Recovery (csrutil only works from Recovery, not Safe Mode): csrutil disable Back in normal macOS: sudo rm /Library/Preferences/com.apple.networkextension.plist Back in Recovery: csrutil enable Restart normally. nesessionmanager rebuilds the NetworkExtension policy state from scratch on next boot. System Settings > Privacy & Security > Local Network shows a clean list afterward (also clears the separate, previously-known bug where every Chrome auto-update was creating a new duplicate entry in that list rather than updating the existing one). Chrome's next navigation attempt to a local IP correctly triggers a fresh permission prompt, and functions normally once granted. Why this matters Apple's own TN3179 states there's no supported way to reset an app's Local Network privilege to "undetermined." This bug compounds that: once nesessionmanager gets stuck in this retry loop, there is no path back to a working state short of disabling SIP and manually deleting a system preference file. The Settings UI also actively misrepresents the true enforcement state (shows "Allow" while nehelper enforces "denied") with no indication to the user that anything is wrong. This is likely not Chrome-specific — any app relying on Local Network access (smart-home apps, casting/streaming apps, IoT config tools) would hit the identical wall once a Mac's nesessionmanager enters this state. Also filed via Feedback Assistant. Happy to share the full decoded netlog trace if useful — didn't attach it here to keep this post scannable, but can paste the additional detail on request.
Replies
2
Boosts
1
Views
609
Activity
2d
Config Profil (Live Caller ID)
Hello everyone, I found an interesting post by a developer on social media that mentions that Apple has provided a new configuration profile. Is this a new feature? Does anyone have any additional information about it? And can anyone provide me with the link to download this profile?
Replies
1
Boosts
0
Views
106
Activity
4d
NEURL Filter configuration approved under wrong Developer Team — resubmission blocked by duplicate domain
Hi, My NEURL Filter configuration (ID 9f3cbff8-63de-4c69-bf68-c19cd1c5d842) was approved on August 5, 2026, but it turned out to be attached to the wrong Apple Developer Team — an old individual account (S5VDH23BBZ) I no longer have access to, instead of my actual organization team, KRKJ76BC7W (SCOTTO), which owns and signs the app (bundle ID com.dropbet.DropBet). I contacted Developer Support, who said they couldn't transfer it and suggested replying to the original approval email — I did (on 08/08, then several times) with no response. I then tried resubmitting the same configuration under the correct team (KRKJ76BC7W), but the portal rejected it with: "A configuration with the same PIR Server Domain already exists." So a fresh submission is technically impossible while the original stays attached to the wrong account — the only real fix is transferring or re-attaching the existing approved configuration to KRKJ76BC7W. Has anyone dealt with this kind of Team ID mix-up before, or know who on the Network Extension / NEURL Filter team could help reassign an approved configuration? Happy to provide any additional details. Thanks in advance.
Replies
13
Boosts
1
Views
1.9k
Activity
5d
Does "Connectivity Assist" bypass NEPacketTunnelProvider DNS interception on iOS 27?
We have a NEPacketTunnelProvider extension that intercepts and modifies DNS responses for specific hostnames as part of its normal operation. On iOS 27, we are seeing this interception being intermittently bypassed. Our extension still receives the DNS query, builds a response, and returns it promptly, but the client occasionally proceeds using a different address, presumably the actual DNS resolution result. This behavior does not reproduce on iOS 26 or earlier. The timing in our logs appears to correlate with the new Connectivity Assist feature (Settings → Wi-Fi), which Apple describes as using cellular data alongside Wi-Fi to improve reliability. Our suspicion is that Connectivity Assist may be performing DNS resolution over a cellular path in parallel, outside the tunnel, causing that resolution path to bypass our provider entirely. We have ruled out response timing and response format issues on our side. Varying the speed and format of our responses does not affect the outcome, suggesting that the behavior is occurring at a layer above the tunnel provider. We have the following questions: Does Connectivity Assist perform DNS resolution on a network path that can bypass an active NEPacketTunnelProvider? Is there any API, entitlement, or supported mechanism to disable Connectivity Assist for an app, or to ensure that all DNS resolution is routed through the active tunnel, similar to previous Wi-Fi Assist opt-out capabilities? Would a NEDNSProxyProvider-based DNS proxy be affected in the same way, or does it operate at a layer that Connectivity Assist cannot bypass? Any guidance, references to relevant documentation, WWDC session content, or confirmation of the expected behavior would be greatly appreciated.
Replies
5
Boosts
0
Views
546
Activity
6d
IKEv2 Personal VPN: Child SA torn down after 120s idle (NEIKEv2ErrorDomain Code=15) with DisconnectOnIdle already NO
We ship a consumer VPN app on iOS, iPadOS and tvOS using a Personal VPN configuration: NEVPNManager with NEVPNProtocolIKEv2, EAP-MSCHAPv2, no MDM profile installed. After exactly 120 seconds with no traffic, iOS destroys the Child SA and disconnects the tunnel. I would like to know whether that timer is configurable, and if not, what the intended mitigation is. From a device sysdiagnose (iPhone, iOS 26.6, build 23G71): NEIPSecDBStatsUpdate: SA is idle for past 120 secs KernelSASession[1, IKEv2 Session Database] idle timeout SA Internal SAID = 2 SPI = C53320D1 Direction = Outbound ChildSA[1] state Connected -> Disconnected error Domain=NEIKEv2ErrorDomain Code=15 "IdleTimeout" <NEIKEv2Provider: Primary Tunnel>: stopping tunnel since Child disconnected nesessionmanager: plugin disconnected with reason "Tunnel was idle for too long" This happened 12 times across a 9.2 hour overnight capture on one idle device. Median time before the tunnel re-established was 14m35s. DISCONNECT-ON-IDLE IS NOT ENABLED The same sysdiagnose shows the plugin's own configuration as: disconnectOnIdle = NO disconnectOnIdleTimeout = 0 These are the stock defaults. There is no disconnectOnIdle property on the public NEVPNProtocol, so a Personal VPN app cannot set them either way. The installed SA parameters contain no idle field at all, only "Lifetime Seconds = 1800", which is honoured correctly: the same capture shows 20 clean rekey cycles. WHAT I HAVE RULED OUT iOS logs a distinct stop reason for each of the following, and none of them occurred across 24 teardowns: "On Demand Disconnect rule matched", "Tunnel was terminated by the server", "Server is not responding", "Network changed, tunnel no longer viable", "Device went to sleep", "Stop command received". The only reason recorded was "Tunnel was idle for too long". On the gateway (strongSwan), IKE rekey and reauth are disabled, uniqueids is never, and DPD is answered in roughly 200ms right up to the teardown. The gateway considers the tunnel healthy at the moment iOS tears it down. DPD does not reset the timer, which makes sense: DPD is an INFORMATIONAL exchange on the IKE SA, whereas the log shows the timer measuring the OUTBOUND Child SA (SAID 2). THE APP CANNOT SEE THIS HAPPEN NEVPNConnection.fetchLastDisconnectError() returns nil for this teardown, because the error is in NEIKEv2ErrorDomain rather than NEVPNConnectionErrorDomain. The app has no supported way to detect that the tunnel dropped for this reason, or to distinguish it from a user-initiated disconnect. It is only visible in a sysdiagnose. TRAFFIC IS NOT HELD DURING THE RECONNECT With Connect On Demand enabled (NEOnDemandRuleConnect, interfaceTypeMatch .any), traffic after the teardown does not wait for the tunnel. On device wake following one of these drops: 01:32:43 device wakes 01:32:43 [C331 ... :443] path:satisfied @0.001s, interface: en0[802.11], uses wifi 01:32:44 [C331 ... :443] flow:finish_connect @0.623s (over en0) 01:32:47 tunnel status changed to connected so flows complete over the physical interface for several seconds before the VPN re-establishes. On-demand triggered the reconnect but did not delay the traffic. For a VPN product this window is the part that concerns me most. QUESTIONS Is the 120 second Child SA idle timeout configurable for a Personal VPN using NEVPNProtocolIKEv2, from the app or from a configuration profile? If DisconnectOnIdle / DisconnectOnIdleTimer are meant to control it, why does the teardown occur when they are NO / 0? If it is not configurable, is application-generated keepalive traffic the intended workaround? If so, how is that expected to work while iOS has the app suspended, which is exactly when a tunnel goes idle? Would server originated traffic that elicits a client response be a supported approach? Is there any supported way for an app to be notified of this teardown, given that fetchLastDisconnectError() returns nil for it? Is the behaviour in "traffic is not held during the reconnect" expected for NEOnDemandRuleConnect, or should matching flows be delayed until the tunnel is up? Is includeAllNetworks the only supported way to close that window? Happy to supply the full sysdiagnose privately.
Replies
3
Boosts
0
Views
408
Activity
1w
Best practices for parallelizing handleNewFlow(_:) or async verdicts in NETransparentProxyProvider
Hello, I am working on network traffic analyser for DLP. The solution is based on NETransparentProxyProvider due to necessity data modifying. To my knowledge, the Transparent Proxy API does not have a concept of an "asynchronous/delayed verdict" for flow authorization. The handleNewFlow(_:) method appears to be invoked sequentially for each new network flow that matches our interception rules. The challenge we are facing is that a portion of our proxy rules and configurations resides in a LaunchDaemon, which the Network Extension communicates with via XPC. Such separation is required by our cross-platform code base. Thus, we are forced to make a synchronous XPC call within handleNewFlow(_:) and block the thread while waiting for the interception verdict from the daemon. This creates a significant bottleneck when establishing new network flows. The current implementation works without issues, but we are always looking for ways to improve performance. Are there any best practices or recommended approaches for parallelizing handleNewFlow(_:)? Thank you in advance.
Replies
2
Boosts
0
Views
213
Activity
1w
Memory leak in CFNetwork (PACClient/PACQuery) when using NETransparentProxyProvider with Auto Proxy Discovery enabled
Hello, I have encountered unexpected behavior when running a Network Extension that implements NETransparentProxyProvider. This extension is part of a DLP (Data Loss Prevention) solution. If the "Auto proxy discovery" option is enabled for the Wi-Fi connection on the managed host, the leaks tool reports memory leaks with the following root cycles: ... 11 (1.03K) ROOT CYCLE: <CFRunLoopSource 0xa430cc540> [192] 10 (864 bytes) ROOT CYCLE: <std::__shared_ptr_emplace<PAC::PACClient> 0xa430f4000> [224] CYCLE BACK TO <CFRunLoopSource 0xa430cc540> [192] 6 (400 bytes) ROOT CYCLE: <std::__shared_ptr_emplace<PAC::PACQuery> 0xa43118080> [128] 2 (80 bytes) ROOT CYCLE: 0xa42ca9000 [32] 1 (48 bytes) ROOT CYCLE: <__NSMallocBlock__ 0xa42804de0> [48] CFNetwork invocation function for block in PAC::PACClient::initialize(void const*, __CFURL cons..." 1 (32 bytes) ROOT CYCLE: <std::__shared_ptr_pointer<BlockHolderVar<__CFString const*, __CFData const*, std::shared_ptr<__CFArray const>, std::shared_ptr<__CFError>>*, SmartBlockWithArgs<__CFString const*, __CFData const*, std::shared_ptr<__CFArray const>, std::shared_ptr<__CFError>>::Deleter> 0xa4343da80> [32] 2 (160 bytes) <NSURL 0xa42840310> [112] 1 (48 bytes) _clients --> <CFString 0xa42c08fc0> [48] 1 (160 bytes) <NWConcrete_nw_pac_resolver 0xa4280cb40> [160] 1 (48 bytes) <CFError 0xa42804ed0> [48] 1 (32 bytes) <std::__shared_ptr_pointer<__CFError*, Deleter_CFRelease> 0xa4343dc20> [32] ... 11 (1.03K) ROOT CYCLE: <CFRunLoopSource 0xa43128540> [192] 10 (864 bytes) ROOT CYCLE: <std::__shared_ptr_emplace<PAC::PACClient> 0xa430f4a80> [224] CYCLE BACK TO <CFRunLoopSource 0xa43128540> [192] 6 (400 bytes) ROOT CYCLE: <std::__shared_ptr_emplace<PAC::PACQuery> 0xa43118880> [128] 2 (80 bytes) ROOT CYCLE: 0xa428105e0 [32] 1 (48 bytes) ROOT CYCLE: <__NSMallocBlock__ 0xa428887b0> [48] CFNetwork invocation function for block in PAC::PACClient::initialize(void const*, __CFURL cons..." 1 (32 bytes) ROOT CYCLE: <std::__shared_ptr_pointer<BlockHolderVar<__CFString const*, __CFData const*, std::shared_ptr<__CFArray const>, std::shared_ptr<__CFError>>*, SmartBlockWithArgs<__CFString const*, __CFData const*, std::shared_ptr<__CFArray const>, std::shared_ptr<__CFError>>::Deleter> 0xa4343f5c0> [32] 2 (160 bytes) <NSURL 0xa428424c0> [112] 1 (48 bytes) _clients --> <CFString 0xa42c0a640> [48] 1 (160 bytes) <NWConcrete_nw_pac_resolver 0xa4280d7c0> [160] 1 (48 bytes) <CFError 0xa42888390> [48] 1 (32 bytes) <std::__shared_ptr_pointer<__CFError*, Deleter_CFRelease> 0xa4343f4c0> [32] ... The extension creates an nw_connection_t to the remote host for each handled flow like this: nw_parameters_t parameters = nw_parameters_create_secure_tcp(NW_PARAMETERS_DISABLE_PROTOCOL, NW_PARAMETERS_DEFAULT_CONFIGURATION); nw_endpoint_t connectTo = nw_endpoint_create_host([endpoint.hostname UTF8String], [endpoint.port UTF8String]); nw_connection_t connection = nw_connection_create(connectTo, parameters); When "Auto proxy discovery" is disabled, everything works as expected, and no memory leaks or issues are observed. Could you please advise on how to resolve or work around this issue? Thank you in advance!
Replies
2
Boosts
0
Views
529
Activity
2w
Potential Network Extension memory leak
I've been investigating memory consumption issues with a network extension. It seems as though the "leak" exists within the network extension framework. Looking at a memory graph, there appears to be a _socketFlows dictionary or similar in the network extension framework that holds all of the flows and they continue to pile up and consume memory. The problem seems to be particularly bad when I leave my MacBook plugged in and running over the weekend and come back on Monday. For instance, in that time period, the memory consumption grew from <100MB to >1GB. To try to narrow this down to see if it was an "us" problem or truly in the framework, I installed Objective-See Foundation's Lulu tool, which also uses a network extension. It similarly saw memory grow from around 30MB or less to 600MB in the same time frame. Has anyone else seen this? We do have an open feedback ticket FB18731867 that might be related that has to do with seeing multiple instances of the same data filter provider started and maybe running in a network extension.
Replies
5
Boosts
0
Views
791
Activity
2w
NEPacketTunnelProvider for local device wide bandwidth pacing without a remote VPN server
We are developing a consumer digital wellbeing app and are evaluating an optional feature that deliberately slows network traffic when explicitly enabled by the user. The proposed architecture is: NEPacketTunnelProvider device wide traffic no MDM or per-app VPN no remote VPN server packet processing/forwarding performed locally on device no application layer inspection no traffic profiling or advertising use no network data uploaded to our servers bounded latency/bandwidth shaping only user can disable it immediately The goal is user controlled “intentional bandwidth” rather than security, privacy filtering, circumvention, or remote VPN access. I’d like to clarify: Is NEPacketTunnelProvider an appropriate public API for this kind of local device wide bandwidth pacing? Is it supported to consume packets via packetFlow, process/forward them locally, and send them to the public Internet without a remote VPN endpoint? Is intentional bandwidth/latency shaping considered an acceptable use of the Network Extension entitlement? Would this still be treated as a “VPN service” for App Review purposes even though no traffic is routed through a remote VPN service? If so, would the organization-enrollment requirement for VPN apps apply? Is there another public API Apple recommends for this use case? I understand that the App Review questions may ultimately need to be answered by App Review rather than the forums. My main goal here is to validate whether this is a supported NetworkExtension architecture before investing in the packet processing implementation.
Replies
1
Boosts
0
Views
213
Activity
2w
NEURLFilter & Live Caller ID Lookup approved, but PIR token issuer directory returns 404 on device — filter never activates (serverSetupIncomplete)
Our NEURLFilter (URL Filter) and Live Caller ID Lookup entitlements are both Approved, but on device the URL filter never activates — the NEURLFilter session loops starting → stopped, so no URLs are filtered. App: bundle app.scamzero (App ID 6768433709) URL Filter Config ID: 224e33c9-b03b-44ce-87cd-2153c86908ce Live Caller ID Lookup Config ID: 0abdc42b-c98b-4753-8af4-9334d42abdb8 Device: iPhone on iOS 26. Test account has the paid plan that enables the feature. The system log shows the failure is fetching the Privacy Pass token issuer directory through Apple's relay (gateway.icloud.com), not our backend: ciphermld : Failed to fetch Token Issuer Directory. URL: https://gateway.icloud.com/pat-issuer-directory?issuer=issuer.scamzero.app Status Code: 404 → NEURLFilterPlugin setStatus:error — NEAgentURLFilterErrorDomain Code=3 → NESMURLFilterSession status changed to stopping with error 3 → prefilterCleanup (removes the prefilter mmap) → session restarts → loop This looks like NEURLFilterManager.Error.serverSetupIncomplete (9). What we have already verified on our side (everything conforms): We run Apple's unmodified reference PIRService (from apple/pir-service-example). Our issuer directory at https://issuer.scamzero.app/.well-known/private-token-issuer-directory returns 200 with valid token-keys (token-type 2) and issuer-request-uri. OHTTP gateway (gateway.scamzero.app/ohttp-configs and /gateway), PIR service (service.scamzero.app), use case app.scamzero.url.filtering, bearer token AAAA, DNS TXT apple-url-filter=app.scamzero, and the validation URL apple.com/url-filter-test = 1 are all configured. Our pre-launch checks pass. The precomputed Bloom prefilter loads on device with the correct bit/hash/seed and ~37k domains. Key observation: our OHTTP gateway logs show zero successful OHTTP requests from Apple's infrastructure since Aug 10, and gateway.icloud.com/pat-issuer-directory?issuer=issuer.scamzero.app returns 404 on device. This suggests our Privacy Pass issuer is not registered/validated in Apple's PAT issuer directory, even though both entitlements are Approved. Question: How can we get our Privacy Pass issuer (issuer.scamzero.app) registered/validated in Apple's token issuer directory for the use cases app.scamzero.url.filtering and Live Caller ID Lookup? The entitlements are approved, but the on-device token issuer directory lookup fails with 404, which prevents the filter from ever activating. Happy to file a Feedback with a sysdiagnose if useful.
Replies
1
Boosts
0
Views
325
Activity
3w
SecKeychainGetStatus returns varying errors after SecKeychainOpen succeeds on macOS 26.6.2
Hi, so far i've been using the following API Call Sequence in order to check the keychain status prior to using one of the certificates inside the keychain for signing or verifying remote tls connection. // Step 1: Open keychain SecKeychainRef keychain = nil; OSStatus status = SecKeychainOpen(keychainPath, &keychain); // Result: errSecSuccess (0) — always succeeds, returns valid ref // Step 2: Get keychain status SecKeychainStatus keychainStatus = 0; status = SecKeychainGetStatus(keychain, &keychainStatus); // Result: FAILS : see table below I observed SecKeychainGetStatus return errSecInternalError or errSecInvalidHandle │ Process │ Keychain │ Error │ Code │ │ GUI App │ login.keychain. | errSecInternalError │ -26276 │ │ GUI App │ login.keychain | errSecInvalidHandle │ -25308 │ │ GUI App │ /Library/Keychains/System.keychain | errSecInvalidHandle │ -25308 │ │ Daemon │ /Library/Keychains/System.keychain │ errSecInvalidHandle │ -25308 │ SecKeychainOpen always returns errSecSuccess with a non-null SecKeychainRef SecKeychainGetStatus fails immediately when called on that reference The error for login.keychain varies between calls (not deterministic) The error for System.keychain is consistently errSecInvalidHandle Issue is 100% reproducible. every keychain access attempt fails Started immediately after macOS 26.6.2 upgrade Do you know if there were any changes in 26.6.2 that could have caused this behavior? If this is expected, how should I address it? Thanks
Replies
1
Boosts
0
Views
246
Activity
3w
IncludeAllNetworks with excludeRoutes do not seem to work together
When I set IncludeAllNetworks in my VPN profile along with the excludeRoutes for traffic I want to go outside of the tunnel, I see the exclude routes being added to the the route table but when we try and to connect on those hosts something is setting a route to the TUN interface and ignoring the excluded routes in the settings. I'm assuming that some policy before any route table is being looked at is doing this but does that then mean IncludeAllNetworks=true and excludeRoutes are actually incompatible? Is this documented and I just missed it?
Replies
2
Boosts
0
Views
205
Activity
3w
NEURLFilterControlProvider: stop(reason:) always receives .none, and is skipped in some teardown paths
Testing NEURLFilterControlProvider on iOS 26.6, I've measured two behaviours around stop(reason:) that I can't reconcile with the documentation, and I'd like to know whether either is intended. Setup: development-signed build, filter configured and running, instrumented stop(reason:) that logs the reason and then emits elapsed-time markers. All timings below are from device log archives collected with "log collect --device". The reason parameter is always .none nesessionmanager records a specific stop reason for itself, then passes .none (0) to the provider. Two different user actions, two different internal reasons, same .none delivered. Disabling via NEURLFilterManager (isEnabled = false, saveToPreferences): 15:20:12.173 nesessionmanager: Setting last stop reason to 31 15:20:12.175 MyProvider: stop() entered, reason=0 (none) 15:20:14.208 MyProvider: stop() completed after 2032ms 15:20:14.209 nesessionmanager: status changed to disconnected, last stop reason Configuration was disabled Disabling the filter from iOS Settings: 15:25:04.167 nesessionmanager: Setting last stop reason to 1 15:25:04.169 MyProvider: stop() entered, reason=0 (none) 15:25:06.198 MyProvider: stop() completed after 2028ms 15:25:06.200 nesessionmanager: status changed to disconnected, last stop reason Stop command received The system clearly knows why the filter stopped, and the values it uses (31, 1) are outside the NEProviderStopReason range, so there appears to be a richer internal enum that isn't mapped through to the provider. Is .none what a URL filter control provider should expect in all cases? If so, is there any supported way for the provider to distinguish a deliberate user disable from system settings, say, an in-app disable, app update or a configuration reload? Is stop(reason:) guaranteed to be called? I do cleanup work in stop(), so I want to know whether it is a reliable lifecycle callback or a best-effort one. I found at least one teardown path where it is skipped entirely. When the configuration is removed as part of the app being deleted, the session is torn down without the provider ever being invoked. The extension is terminated roughly 355 ms before the configuration removal: 15:33:59.361 launchd: service inactive: MyProvider, leaving extension group 15:33:59.363 runningboardd: Executing termination request 15:33:59.431 launchd: remove all extension instances ... total of 0 extension instances were found to remove 15:33:59.716 nesessionmanager: configuration has been removed, stopping 15:33:59.716 nesessionmanager: Setting last stop reason to 32 15:33:59.717 nesessionmanager: plugin NEURLFilterPlugin(...) disposed 15:33:59.717 nesessionmanager: status changed to disconnected, last stop reason Configuration was removed No stop() call appears anywhere in the archive, and the plugin is disposed rather than stopped. The whole teardown takes one millisecond. The ordering looks like the cause: by the time nesessionmanager removes the configuration, the extension process is already gone, so there is nothing left to call stop() on. Thread 713975 describes stopTunnel being called with .providerDisabled in the equivalent situation for a packet tunnel provider, so the behaviour appears to differ by provider type. So my question is about the contract rather than this specific path: should a URL filter control provider treat stop(reason:) as guaranteed, or as best-effort? If it is best-effort, are there other teardown paths where it is skipped, so I can make sure nothing important depends on it running?
Replies
1
Boosts
0
Views
453
Activity
3w
Can Multiple Transparent Proxies Work Simultaneously on macOS?
Hi everyone, I’m looking for some clarification regarding "Transparent Proxy configurations on macOS". We have a requirement where multiple security/networking solutions may need to use a "Transparent Proxy" on the same macOS device. For example, one solution may be used for web security/SSL inspection while another solution is required for a different security or network traffic use case. My questions are: 1.Does macOS support configuring and running multiple Transparent Proxy providers simultaneously on the same device? 2. If multiple Transparent Proxy configurations are deployed through MDM, how does macOS determine which proxy handles the traffic? 3.Is there any limitation on the number of Transparent Proxy configurations/providers that can be configured? 4.Can multiple Transparent Proxy providers coexist if they handle different traffic, applications, or network interfaces? 5. If multiple providers are configured, is there a supported mechanism to define priority, ordering, or traffic routing between them? 6. Are there any differences or limitations across recent macOS versions, particularly macOS 15 and macOS 26? 7. Is there any Apple documentation or recommended architecture for deploying multiple Transparent Proxy providers on the same Mac? If anyone has implemented or tested a setup with multiple Transparent Proxy providers on macOS, I’d appreciate any insights, documentation, or real-world experience. Thanks! Vijay
Replies
1
Boosts
0
Views
373
Activity
3w